"""
西门子PLC客户端模块

支持西门子S7系列PLC的Modbus TCP通信
"""

import logging
from typing import List, Optional
from pymodbus.client import ModbusTcpClient
from pymodbus.exceptions import ModbusException, ConnectionException
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadBuilder

from .base import BasePLCClient

logger = logging.getLogger(__name__)


class SiemensS7ModbusTCPClient(BasePLCClient):
    """西门子S7系列PLC的Modbus TCP通信客户端"""

    # 西门子S7系列PLC的Modbus地址映射(基于常见配置)
    # V区映射到保持寄存器 Holding Registers (功能码 3, 6, 16)
    # 地址范围: 40001 - 49999
    # 例如: V4.0 对应地址 40005 (字)
    HOLDING_V = 0  # V区在Modbus上通常从地址0开始

    # I区映射到离散量输入 Discrete Inputs (功能码 2)
    # 地址范围: 10001 - 19999
    # 例如: I0.0 对应地址 10001
    INPUT_I = 0 # I区在Modbus上通常从地址0开始

    # Q区映射到线圈 Coils (功能码 1, 5, 15)
    # 地址范围: 00001 - 09999
    # 例如: Q0.0 对应地址 1
    COIL_Q = 0 # Q区在Modbus上通常从地址0开始

    def __init__(self, host="127.0.0.1", port=502, timeout=5.0, slave_id=1):
        """
        初始化西门子S7 Modbus TCP客户端.
        
        参数:
            host (str): PLC的IP地址.
            port (int): Modbus TCP端口，默认为502.
            timeout (float): 通信超时时间，增加到5秒以提高稳定性.
            slave_id (int): 从站地址.
        """
        super().__init__()
        self.host = host
        self.port = port
        self.timeout = timeout
        self.slave_id = slave_id

    def connect(self) -> bool:
        """连接到PLC设备."""
        try:
            # 如果已有客户端实例，先尝试关闭
            if self.client and hasattr(self.client, 'is_socket_open') and self.client.is_socket_open():
                self.client.close()

            self.client = ModbusTcpClient(
                host=self.host,
                port=self.port,
                timeout=self.timeout
            )
            
            if self.client.connect():
                self.connected = True
                logger.info(f"成功连接到西门子PLC: {self.host}:{self.port}")
                return True
            else:
                self.connected = False
                logger.error(f"无法连接到西门子PLC: {self.host}:{self.port}")
                return False
        except Exception as e:
            self.connected = False
            logger.error(f"西门子PLC连接异常: {e}")
            return False

    def close(self) -> None:
        """关闭连接."""
        if self.client:
            self.client.close()
            self.connected = False
            logger.info(f"已关闭与西门子PLC的连接: {self.host}:{self.port}")
    
    def test_connection(self) -> bool:
        """测试连接并尝试读取基本信息"""
        if not self.is_connected():
            logger.error("客户端未连接，无法进行测试")
            return False
            
        # 尝试读取地址0的寄存器，这通常是最基本的测试
        test_addresses = [0, 1, 40001, 40000]
        
        for addr in test_addresses:
            try:
                logger.info(f"测试读取地址: {addr}")
                result = self.client.read_holding_registers(address=addr, count=1, slave=self.slave_id)
                if not result.isError():
                    logger.info(f"测试成功！地址 {addr} 返回: {result.registers}")
                    return True
                else:
                    logger.warning(f"地址 {addr} 测试失败: {result}")
            except Exception as e:
                logger.warning(f"地址 {addr} 测试异常: {e}")
                
        logger.error("所有测试地址都失败，可能设备不支持Modbus或配置有误")
        return False

    def read_v_registers(self, start_address: int, count: int = 1) -> Optional[List[int]]:
        """
        读取V区寄存器(映射到Holding Registers).
        
        参数:
            start_address (int): 起始V区地址 (例如, 100 for VW100).
            count (int): 读取的寄存器数量.
            
        返回:
            寄存器值列表或None.
        """
        if not self.is_connected():
            logger.error("客户端未连接")
            return None
            
        # 尝试多种地址计算方式，以兼容不同的西门子设备配置
        address_attempts = [
            start_address,  # 直接使用地址
            start_address - 40001,  # 从40001减去基址
            start_address // 2,  # 某些设备可能需要字节地址转字地址
        ]
        
        for attempt_idx, modbus_address in enumerate(address_attempts):
            if modbus_address < 0:
                continue
                
            try:
                logger.info(f"尝试读取方式{attempt_idx + 1}: Modbus地址={modbus_address}")
                result = self.client.read_holding_registers(
                    address=modbus_address,
                    count=count,
                    slave=self.slave_id
                )
                if not result.isError():
                    logger.info(f"成功读取V区地址{start_address} (Modbus地址={modbus_address}): {result.registers}")
                    return result.registers
                else:
                    logger.warning(f"读取方式{attempt_idx + 1}失败: {result}")
                    
            except (ConnectionException, OSError) as e:
                logger.warning(f"读取V区时连接出错: {e}。正在尝试重新连接...")
                if self.connect():
                    try:
                        result = self.client.read_holding_registers(address=modbus_address, count=count, slave=self.slave_id)
                        if not result.isError():
                            logger.info(f"重试成功读取V区地址{start_address}: {result.registers}")
                            return result.registers
                    except Exception as e_retry:
                        logger.error(f"重试读取V区时发生错误: {e_retry}")
                return None
            except Exception as e:
                logger.warning(f"读取方式{attempt_idx + 1}时发生错误: {e}")
                continue
                
        logger.error(f"所有读取方式都失败，无法读取V区地址{start_address}")
        return None

    def write_v_registers(self, start_address: int, values: List[int]) -> bool:
        """
        写入V区寄存器(映射到Holding Registers).
        
        参数:
            start_address (int): 起始V区地址.
            values (List[int]): 要写入的值.
            
        返回:
            成功返回True, 失败返回False.
        """
        if not self.is_connected():
            logger.error("客户端未连接")
            return False
        try:
            modbus_address = self.HOLDING_V + start_address
            
            # 确保值在16位有符号整数范围内 (-32768 到 32767)
            validated_values = []
            for value in values:
                if isinstance(value, str):
                    try:
                        value = int(value)
                    except ValueError:
                        logger.error(f"无法将字符串 '{value}' 转换为整数")
                        return False
                
                # 限制值在16位有符号整数范围内
                if value < -32768:
                    value = -32768
                    logger.warning(f"值被限制为最小值: -32768")
                elif value > 32767:
                    value = 32767
                    logger.warning(f"值被限制为最大值: 32767")
                
                validated_values.append(value)
            
            # 直接使用值列表，不使用BinaryPayloadBuilder
            result = self.client.write_registers(modbus_address, validated_values, slave=self.slave_id)
            if not result.isError():
                logger.info(f"成功写入V区地址{start_address}: {validated_values}")
                return True
            else:
                logger.error(f"写入V区地址{start_address}失败: {result}")
                return False
        except (ConnectionException, OSError) as e:
            logger.warning(f"写入V区时连接出错: {e}。正在尝试重新连接并重试...")
            if self.connect():
                try:
                    result = self.client.write_registers(modbus_address, validated_values, slave=self.slave_id)
                    if not result.isError():
                        logger.info(f"重试成功写入V区地址{start_address}: {validated_values}")
                        return True
                except Exception as e_retry:
                    logger.error(f"重试写入V区时发生错误: {e_retry}")
            return False
        except Exception as e:
            logger.error(f"写入V区时发生错误: {e}")
            return False

    def read_i_inputs(self, start_address: int, count: int = 1) -> Optional[List[bool]]:
        """
        读取I区输入(映射到Discrete Inputs).
        
        参数:
            start_address (int): 起始I区地址 (例如, 0 for I0.0).
            count (int): 读取的输入数量.
            
        返回:
            状态位列表(True/False)或None.
        """
        if not self.is_connected():
            logger.error("客户端未连接")
            return None
        try:
            modbus_address = self.INPUT_I + start_address
            result = self.client.read_discrete_inputs(
                address=modbus_address,
                count=count,
                slave=self.slave_id
            )
            if not result.isError():
                logger.info(f"成功读取I区地址{start_address}: {result.bits}")
                return result.bits
            else:
                logger.error(f"读取I区地址{start_address}失败: {result}")
                return None
        except (ConnectionException, OSError) as e:
            logger.warning(f"读取I区时连接出错: {e}。正在尝试重新连接并重试...")
            if self.connect():
                try:
                    result = self.client.read_discrete_inputs(address=modbus_address, count=count, slave=self.slave_id)
                    if not result.isError():
                        logger.info(f"重试成功读取I区地址{start_address}: {result.bits}")
                        return result.bits
                except Exception as e_retry:
                    logger.error(f"重试读取I区时发生错误: {e_retry}")
            return None
        except Exception as e:
            logger.error(f"读取I区时发生错误: {e}")
            return None

    def read_q_coils(self, start_address: int, count: int = 1) -> Optional[List[bool]]:
        """
        读取Q区线圈(映射到Coils).
        
        参数:
            start_address (int): 起始Q区地址 (例如, 0 for Q0.0).
            count (int): 读取的线圈数量.
            
        返回:
            状态位列表(True/False)或None.
        """
        if not self.is_connected():
            logger.error("客户端未连接")
            return None
        try:
            modbus_address = self.COIL_Q + start_address
            result = self.client.read_coils(
                address=modbus_address,
                count=count,
                slave=self.slave_id
            )
            if not result.isError():
                logger.info(f"成功读取Q区地址{start_address}: {result.bits}")
                return result.bits
            else:
                logger.error(f"读取Q区地址{start_address}失败: {result}")
                return None
        except (ConnectionException, OSError) as e:
            logger.warning(f"读取Q区时连接出错: {e}。正在尝试重新连接并重试...")
            if self.connect():
                try:
                    result = self.client.read_coils(address=modbus_address, count=count, slave=self.slave_id)
                    if not result.isError():
                        logger.info(f"重试成功读取Q区地址{start_address}: {result.bits}")
                        return result.bits
                except Exception as e_retry:
                    logger.error(f"重试读取Q区时发生错误: {e_retry}")
            return None
        except Exception as e:
            logger.error(f"读取Q区时发生错误: {e}")
            return None

    def write_q_coils(self, start_address: int, values: List[bool]) -> bool:
        """
        写入Q区线圈.
        
        参数:
            start_address (int): 起始Q区地址.
            values (List[bool]): 要写入的状态.
            
        返回:
            成功返回True, 失败返回False.
        """
        if not self.is_connected():
            logger.error("客户端未连接")
            return False
        try:
            modbus_address = self.COIL_Q + start_address
            result = self.client.write_coils(modbus_address, values, slave=self.slave_id)
            if not result.isError():
                logger.info(f"成功写入Q区地址{start_address}: {values}")
                return True
            else:
                logger.error(f"写入Q区地址{start_address}失败: {result}")
                return False
        except (ConnectionException, OSError) as e:
            logger.warning(f"写入Q区时连接出错: {e}。正在尝试重新连接并重试...")
            if self.connect():
                try:
                    result = self.client.write_coils(modbus_address, values, slave=self.slave_id)
                    if not result.isError():
                        logger.info(f"重试成功写入Q区地址{start_address}: {values}")
                        return True
                except Exception as e_retry:
                    logger.error(f"重试写入Q区时发生错误: {e_retry}")
            return False
        except Exception as e:
            logger.error(f"写入Q区时发生错误: {e}")
            return False

    def get_device_info(self) -> dict:
        """获取设备信息"""
        info = super().get_device_info()
        info.update({
            "manufacturer": "Siemens",
            "model": "S7",
            "communication": "Modbus TCP",
            "host": self.host,
            "port": self.port,
            "slave_id": self.slave_id
        })
        return info
